/*
 * Copyright (C) 2011 Johan Maasing johan at zoom.nu Licensed under the Apache
 * License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package nu.zoom.catonine.tail;

import java.io.IOException;
import java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Adds functionality to run a thread that regularly calls the read method to
 * detect new content in the tracked file.
 * 
 * @author "Johan Maasing" &lt;johan@zoom.nu&gt;
 */
abstract class AbstractThreadedTailer extends AbstractExecutorTailer implements Runnable, Tailer {

	private final Log log = LogFactory.getLog(getClass());
	private Thread tailThread;
	private boolean tail = false;
	private static final long SLEEP_TIME = 1234;
	/**
	 * The thread group used for notifications and other threads the tailer
	 * might use.
	 */
	protected final ThreadGroup tailThreadGroup = new ThreadGroup("Tailer");

	@Override
	public synchronized void run() {
		log.trace("Tail thread started");
		while (tail) {
			try {
				fireWork(true);
				read();
				fireWork(false);
				try {
					wait(SLEEP_TIME);
				} catch (InterruptedException e) {
					// We probably was stopped from another thread and the
					// tail flag should already be set to false but set it
					// anyway.
					tail = false;
					log.debug("Tail thread interrupted", e);
					// Set the interrupted flag
					Thread.currentThread().interrupt();
				}
			} catch (IOException e) {
				ArrayList<TailerListener.LogEntry> errorMessages = new ArrayList<TailerListener.LogEntry>();
				errorMessages.add(new TailerListener.LogEntry(0, "IOException while tailing"));
				errorMessages.add(new TailerListener.LogEntry(26, e.toString()));
				errorMessages.add(new TailerListener.LogEntry(e.toString().length() + 26, "Aborting tail thread"));
				fireLinesRead(errorMessages);
				tail = false;
				log.error("Aborting tail thread", e);
			}
		}

		log.trace("Tail thread exiting");
		fireStopped();
		notifyAll();
		executor.shutdown();
	}

	/**
	 * Indicates if the tailer is tailing.
	 * 
	 * @return true if the tailer has not been told to stop and is active.
	 */
	@Override
	public synchronized boolean isTailing() {
		return ((tail) && (tailThread.isAlive()));
	}

	/**
	 * Tell the tailer to start tailing. Subclasses should implement the setup
	 * method to initialize the state and the validateState method to signal if
	 * it can not be started.
	 */
	@Override
	public synchronized void start() throws IllegalStateException {
		if (tailThread == null) {
			log.trace("Initializing tail thread");
			tail = true;
			tailThread = new Thread(tailThreadGroup, this, "Tail thread");
			tailThread.setDaemon(true);
			tailThread.start();
		}
	}

	/**
	 * Inform the tailer to stop. Subclasses should override the
	 * 
	 * @link{#stopped method to perform clean up.
	 */
	@Override
	public synchronized void stop() {
		log.trace("Stopping tail thread");
		tail = false;
		if (tailThread != null) {
			log.trace("Interrupting tail thread");
			tailThread.interrupt();
			log.debug("Interrupted thread: " + tailThread);
			tailThread = null;
		}
	}

	/**
	 * Subclasses can use this method to inform the tailer to stop and wait for
	 * it to have stopped. This method does not return until the tailer has
	 * stopped tailing.
	 */
	protected void stopAndWait() {
		stop();
		while (isTailing()) {
			try {
				wait(123);
			} catch (InterruptedException e) {
				log.debug("Interrupted while waiting for stop");
				// Reset interrupt flag
				Thread.currentThread().interrupt();
			}
		}
	}

	/**
	 * Subclasses should override this method and try to detect new log blocks.
	 * If new blocks are detected the class should fire an event to registered
	 * listeners to let them know of the new log blocks.
	 * 
	 * @throws java.io.IOException
	 *             May be used to signal that reading failed. This will cause
	 *             the tailer to stop.
	 */
	protected abstract void read() throws IOException;
}
